using System;
using System.IO;

namespace Meta {
	/// <summary>
	/// Summary description for RTFTextReader.
	/// </summary>
	public class RTFTextReader : TextReader {
		TextReader raw;
		char[] command = new char[40];
		int commandArgument;
		int commandLength = 0;
		const int gotViewkind = -2;
		int currentChar;
		int charOffset=0;

		public RTFTextReader(TextReader rawReader)	{
			raw = rawReader;
			// Skip past the \viewkind4 command.
			while (Read()!=gotViewkind);
		}

		public override void Close() {
			raw.Close();
		}

		private void PushState() {}
		private void PopState() {}

		// Read a hex digit and convert it to an integer
		int ReadHexDigit() {
			int ch = raw.Read();

			if ('0'<=ch & ch<='9')
				return ch-'0';
			else if ('a'<=ch & ch<= 'f')
				return ch-'a'+10;
			else if ('A'<=ch & ch<= 'F')
				return ch-'A'+10;
			else
				throw new Exception("Bad file format: malformed hex constant");
		}

		private void ReadCommand(int currentChar) {
			int ch=currentChar;
			bool argNegative=false;

			commandLength=0;
			command[commandLength++] = (char)ch;
			while (char.IsLetter((char)(ch=raw.Peek()))) {
				command[commandLength++] = (char)ch;
				raw.Read();
			}
			// ch is now something other than a letter.
			commandArgument=0;
			if (((char)raw.Peek())=='-') {
				argNegative=true;
				raw.Read();
			}
			while (char.IsDigit((char)(ch=raw.Peek()))) {
				commandArgument = commandArgument*10+ch-'0';
				raw.Read();
			}
			// ch is now neither letter nor digit
			if (argNegative)
				commandArgument = -commandArgument;
			if (ch!='\\')
				raw.Read();  // swallow delimiter
		}

		private bool CommandIs(string cmd) {
			if (cmd.Length != commandLength)
				return false;
			else {
				for (int i = 0; i<cmd.Length; i++)
					if (cmd[i]!=command[i])
						return false;
			}
			return true;
		}
		
		public override int Peek() {
			return currentChar;
		}

		public override int Read() {
			if (currentChar==-1)
				return currentChar;

			int saved = currentChar;
			currentChar = ReadInternal();
			return saved;
		}

		public int ReadInternal() {
			while (true) {
				int ch = raw.Read();

				switch (ch) {
					case -1:   // eof
						return ch;
						//break;

					case '{':
						PushState();
						break;

					case '}':
						PopState();
						break;

					case (char)0:
						return -1;     // RTF files seem to end with a null, so return an EOF.

					case '\\':
						ch=raw.Read();
						switch (ch) {

							case '\'':
								int charSpec = (ReadHexDigit()*16)+ReadHexDigit();
								// Awful.  It turns out that my kluge for recognizing when a magic character has been loaded from the symbol font doesn't work, so this is a temporary kluge.
								if (charSpec==0xab || charSpec==0xbb) {
									charOffset = 0;
									return charSpec;
								} else
									return charOffset+charSpec;
								//break;

							case '\\':
							case '{':
							case '}':
								return ch;
								//break;

							default:
								ReadCommand(ch);
								if (CommandIs("u"))
									// unicode character constant
									return commandArgument;
								else if (CommandIs("f"))
									charOffset = (commandArgument==1)?(0x3c9-0xf9):0;
								else if (CommandIs("viewkind")) {
									charOffset = 0;
									return gotViewkind;
								} else if (CommandIs("rdblquote"))
									return '\u201d';
								else if (CommandIs("ldblquote"))
									return '\u201c';
								// Not really needed because the newline following it will go through.
								//else if (CommandIs("par"))
								//	return '\n';
								break;
						}
						break;

					default:
						return ch;
				}
			}
		}
	}
}
